<?php
/*
* CHANGELOG
*/

/**
*
*
* TODO list
*       - set convenience functions to easily apply cache-control, redirect, content-type
*
*

*/

class Yz_HTTP_Response
    extends Yz_Class
    implements Yz_HTTP_Response_Interface
{

    protected $view;

    protected $date;

    /** @var string Status - the HTTP/1.1 status
    *       default is '200 OK'
    */
    protected $status = self::STATUS_OK;

    protected $HTTPStatus = array(
        self::STATUS_OK                => 'OK',
        self::STATUS_CREATED           => 'Created',
        self::STATUS_ACCEPTED          => 'Accepted',
        self::STATUS_NO_CONTENT        => 'No Content',
        self::STATUS_PARTIAL_CONTENT   => 'Partial Content',
        self::STATUS_MOVED_PERMANENTLY => 'Moved Permanently',
        self::STATUS_FOUND             => 'Found',
        self::STATUS_SEE_OTHER         => 'See Other',
        self::STATUS_NOT_MODIFIED      => 'Not Modified',
        self::STATUS_TEMPORARY_REDIRECT=> 'Temporary Redirect',
        self::STATUS_BAD_REQUEST       => 'Bad Request',
        self::STATUS_UNAUTHORIZED      => 'Unauthorized',
        self::STATUS_PAYMENT_REQUIRED  => 'Payment Required',
        self::STATUS_FORBIDDEN         => 'Forbidden',
        self::STATUS_NOT_FOUND         => 'Not Found',
        self::STATUS_INTERNAL_SERVER_ERROR => 'Internal Server Error',
    );
    protected $headers = array(
        self::HEADER_ALLOW             => '',
        self::HEADER_CACHE_CONTROL     => '',
        self::HEADER_CONNECTION        => '',
        self::HEADER_CONTENT_ENCODING  => '',
        self::HEADER_CONTENT_LANGUAGE  => '',
        self::HEADER_CONTENT_LENGTH    => '',
        self::HEADER_CONTENT_LOCATION  => '',
        self::HEADER_CONTENT_MD5       => '',
        self::HEADER_CONTENT_RANGE     => '',
        self::HEADER_CONTENT_TYPE      => '',
        self::HEADER_DATE              => '',
        self::HEADER_ETAG              => '',
        self::HEADER_EXPIRES           => '',
        self::HEADER_LAST_MODIFIED     => '',
        self::HEADER_LOCATION          => '',
        self::HEADER_PRAGMA            => '',
        self::HEADER_RETRY_AFTER       => '',
        self::HEADER_SERVER            => '',
        self::HEADER_TRANSFER_ENCODING => '',
        self::HEADER_WWW_AUTHENTICATE  => '',
    );

    protected static $instance;


    /**
    */
    protected function __construct()
    {
        debug();

        $this->date = gmdate( 'r', YZ_TIME );

    } // __construct()



    /**
    * static getInstance():
    *
    */
    public static function getInstance()
    {
        debug();

        $i =& self::$instance;
        if ( null === $i ) {
            $i = new self;
        }

        return $i;
    } // getInstance()



    /** setHeader()
    *   TODO set X-header with a separate method
    *
    */
    public function setHeader( $header, $value )
    {
        debug();

        $value = Yz_String::test( $value, 'Response Header' );

        if( ! array_key_exists( $header, $this->headers ) ) {
            throw new Yz_Exception( "Supplied header Not known" );
        }
        $this->headers[ $header ] = $value;
        return $this;
    } // setHeader()


    /** setContentType()
    *
    */
    public function setContentType( $value )
    {
        debug();

        $value = Yz_String::test( $value, 'Content-Type Header' );

        $this->headers[ self::HEADER_CONTENT_TYPE ] = $value;
        return $this;
    } // setContentType()


    /** getStatus()
    *
    *
    */
    public function getStatus()
    {
        debug();

        return $this->status;
    } // getStatus



    /** setStatus() : set a status
    * XXX what to do when a status is set? XXX
    *
    * @return old status
    */
    public function setStatus( $status )
    {
        debug();

        if ( null === $status ) {
            $status = self::STATUS_OK;
        } else if ( ! array_key_exists( $status, $this->HTTPStatus ) ) {
            debug( $status );
            throw new Yz_Exception( "Invalid value for status" );
        }

        $this->status = $status;
        return $this;
    } // setStatus

    /** alias to redirect
     *
     */
    public function setLocation( $url )
    {
        debug();
        return $this->redirect( $url );
    }


    /** redirect(): actually set the location header
    *
    * @param url string
    * @param const status
    *    @return $this
    */
    public function redirect( $url )
    {
        debug();

        // check URL
        if ( preg_match("~^(http[s]?)://~i", $url ) ) {
            $data = parse_url( $url );
            if( "" !== (string)$data['user'] ) {
                $httpUser = urlencode($data['user']);
                if( "" !== (string)$data['pass'] ) {
                    $httpUser .= ":" . urlencode($data['pass']);
                }
                $httpUser .= "@";
            }

        } else {
            $data = array();
            $rq = Yz_HTTP_Request::getInstance();
            // TODO fetch scheme from request
            $data['scheme'] = "http";
            $httpUser = "";
            $data['host'] = $rq->getHostname();
            $data['path'] = strtok( $url, '?' );
            $data['query'] = strtok( '#' );
            $data['fragment'] = strtok( "\0" ); // up to the end of string
        }

        $redir = $data['scheme'] . "://" . $httpUser . $data['host'] . "/";

        $path = ltrim( $data['path'], '/' );
        if ( "" !== "$path" ) {
            $redir .= str_replace( "%2F", '/', rawurlencode( $path ) );
        }

        $query = $data['query'];
        if ( "" !== "$query" ) {
            $redir .= "?" . $query;
        }

        $hash = $data['fragment'];
        if ( "" !== "$hash" ) {
            $redir .= "#" . rawurlencode( $hash );
        }

        $this->headers[ self::HEADER_LOCATION ] = $redir ;

        debug( 1, "Redirect URL='$redir'" );

        return $this;
    } // redirect()



    /** send(): send the response
    *
    */
    public function send()
    {
        debug();

        // print response text
        $text = $this->getBody() ;

        // send headers
        $this->sendHeaders();

        print $text ;

        return $this;
    } // send



    /** getBody
    *     with the idea that some handlers could modify the Body
    *
    */
    public function getBody()
    {
        debug();
        if ( null !== $this->view ) {
            return $this->view->render( $this );
        }
        if (null === $this->body) {
            throw new Yz_Exception( "View not set or invalid" );
        }
        return $this->body;
    } // getBody

    /** set the request's text Body
    *
    *   @param string
    *   @return chainable
    */
    public function setBody($body)
    {
        $this->body = $body;
        return $this;
    } // setBody

    /** set the request's view which will be converted into text Body
    *
    *   @param Yz_View_Interface
    *   @return chainable
    */
    public function setView(Yz_View_Interface $view)
    {
        debug($view->className());
        $this->view = $view;
        return $this;
    } // setBody


    /** setCacheable
    *   @param bool[true] cacheable
    *   @return chainable
    */
    public function setCacheable( /*bool*/ $cacheable=true )
    {
        debug();

        if ( $cacheable ) {

          $this->headers['Cache-Control'] = 'public, must-revalidate';
        } else {
          $this->headers['Cache-Control'] = 'no-cache';
          $this->headers['Pragma'] = 'no-cache';
          $this->headers['Last-Modified'] = $this->date;
          $this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
        }

        return $this;
    } // setCacheable



    /** sendHeaders() :
    *
    *
    */
    protected function sendHeaders()
    {
        debug();
        $status = $this->status;
        $http = "HTTP/1.1 $status " . $this->HTTPStatus[$status];

        debug($http);
        header( $http );

        if ( '' === $this->headers[self::HEADER_CONTENT_TYPE] ) {
            $this->headers[self::HEADER_CONTENT_TYPE] = 'text/html; charset=utf-8';
        }
        if ( '' === $this->headers[self::HEADER_DATE] ) {
            $this->headers[self::HEADER_DATE] = $this->date;
        }

        foreach ( $this->headers as $h => $v ) {
            if ('' !== $v) {
                header( "$h: $v" );
            }
        } // foreach
 #       debug(join(";\n", headers_list()));
        return $this;
    } // sendHeaders()

} // class

